import React, { useCallback, useMemo, useState } from "react";
import { SVGPathData, encodeSVGPath } from "svg-pathdata";
import {
  keyFor,
  assertNever,
  HelperType,
} from "../../svg/utils";
import parseSvg from "../../svg/parse-svg";
import { red } from "../../svg/colors";
import { cloneDeep } from "lodash";
import { useDebounceFn } from "ahooks";

let resetTimeout;


function useCreateNode(svgObj, darwType, color) {
  const [hovering, setHovering] = useState(null);
  const [selectedStorke, setSelectedStroke] = useState({})
  const [selectedFill, setSelectedFill] = useState({})

  const viewBox = svgObj?.properties?.viewBox?.split(' ') || [0, 0, 1024, 1024]
  const stroke = Math.min(viewBox[2] - viewBox[0], viewBox[3] - viewBox[1]) / 400

  const debounceMouseIn = useDebounceFn((key, type) => {
    if (
      type === HelperType.invisibleFull ||
      type === HelperType.invisible || darwType !== 'stroke'
    ) {
      return;
    }
    if (resetTimeout) {
      clearTimeout(resetTimeout);
    }

    setHovering(key);
  }, { wait: 50 }).run


  const debounceMouseOuter = useDebounceFn((key, type) => {
    if (
      type === HelperType.invisibleFull ||
      type === HelperType.invisible || darwType !== 'stroke'
    ) {
      return;
    }
    resetTimeout = setTimeout(() => setHovering(null), 50);
  }, { wait: 50 }).run

  const debounceFillClick = useDebounceFn(
    (e, key) => {
      if (darwType === 'fill') {
        e.stopPropagation()
        e.preventDefault()

        const tempObj = cloneDeep(selectedFill)
        if (Object.keys(selectedFill).includes(key)) {
          delete tempObj[key]
          setSelectedFill(tempObj)
        } else {
          setSelectedFill({ ...tempObj, [key]: color })
        }
      }
    }, { wait: 1000 }
  ).run


  const fillStyle = useCallback(
    (key, fill) => {
      return {
        style: {
          fill: Object.keys(selectedFill).join('|').includes(key) ? selectedFill[key] : fill,
        },
        onClick: (e) => debounceFillClick(e, key)
      }
    }, [debounceFillClick, selectedFill]
  )

  const style = useCallback(
    (key, type) => {
      return {
        style: {
          color:
            hovering === key
              ? red
              : Object.keys(selectedStorke).includes(key)
                ? selectedStorke[key]
                : type === HelperType.default || type === HelperType.implicit
                  ? "lightgrey"
                  : type === HelperType.invisibleFull ||
                    type === HelperType.invisible ||
                    type === HelperType.defaultChild
                    ? "transparent"
                    : undefined,
          pointerEvents: type === HelperType.invisibleFull ||
            type === HelperType.invisible || darwType !== 'stroke'
            ? "none"
            : "initial",
        },
        stroke: "currentColor",
        fill: "none",
        strokeDasharray:
          type === HelperType.implicit || type === HelperType.invisible
            ? stroke * 2
            : "none",
        strokeWidth: stroke,
        "data-key":
          type === HelperType.invisibleFull || type === HelperType.invisible
            ? undefined
            : key,
        onMouseEnter: () => debounceMouseIn(key, type),
        onMouseLeave: () => debounceMouseOuter(key, type),
        onClick: (e) => {
          e.preventDefault()
          e.stopPropagation()
          if (
            type === HelperType.invisibleFull ||
            type === HelperType.invisible || darwType !== 'stroke'
          ) {
            return;
          }


          const tempObj = cloneDeep(selectedStorke)
          if (Object.keys(selectedStorke).includes(key)) {
            delete tempObj[key]
            setSelectedStroke(tempObj)
          } else {
            setSelectedStroke({ ...tempObj, [key]: color })
          }
        },
      };
    },
    [hovering, selectedStorke, stroke, debounceMouseIn, debounceMouseOuter, darwType, color]
  );

  const propertiesHand = useCallback(
    (props) => {
      const keys = Object.keys(props)
      const otherKV = { id: `${props['render_id']}` }
      keys.forEach(key => {
        // 部分警告替换
        if (!['children', 'tagName'].includes(key)) {
          otherKV[key === "class" ? 'className' : key] = props[key]
        }
      })
      return otherKV
    }, []
  )


  const fillNode = useCallback(
    (props) => {
      const otherKV = propertiesHand(props)
      return <path {...otherKV} {...fillStyle(otherKV.id, otherKV.fill)} key={otherKV.id} />
    }, [fillStyle, propertiesHand]
  )

  const separateNode = useCallback((nodeObj) => {
    const { tagName, properties } = nodeObj
    const { d, render_id } = properties


    switch (tagName) {
      // 后续需要支持其它

      case "path":
        // 解析d
        const parseD = new SVGPathData(d)
        const data = parseD.commands.reduce(
          (prev, c, i, commands) => {
            switch (c.type) {
              case SVGPathData.MOVE_TO: {
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };
                prev.start = next;
                prev.current = next;
                break;
              }
              case SVGPathData.LINE_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };
                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;
                prev.elems.push(
                  <line
                    key={key}
                    x1={prev.current.x}
                    y1={prev.current.y}
                    x2={next.x}
                    y2={next.y}
                    {...style(key)}
                  />
                );
                prev.current = next;
                break;
              }
              case SVGPathData.HORIZ_LINE_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: prev.current.y,
                };
                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;

                prev.elems.push(
                  <line
                    key={key}
                    x1={prev.current.x}
                    y1={prev.current.y}
                    x2={next.x}
                    y2={next.y}
                    {...style(key)}
                  />
                );
                prev.current = next;
                break;
              }
              case SVGPathData.VERT_LINE_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: prev.current.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };
                prev.current = next;
                break;
              }
              case SVGPathData.CLOSE_PATH: {
                if (isNaN(prev.start.x)) {
                  break;
                }
                const next = prev.start;
                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;

                prev.elems.push(
                  <line
                    key={key}
                    x1={prev.current.x}
                    y1={prev.current.y}
                    x2={next.x}
                    y2={next.y}
                    {...style(key)}
                  />
                );
                prev.current = next;
                prev.start = { x: NaN, y: NaN };
                break;
              }
              case SVGPathData.CURVE_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };


                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;

                prev.elems.push(
                  <path
                    key={`${key}`}
                    d={`M ${prev.current.x},${prev.current.y} ${encodeSVGPath(c)}`}
                    {...style(key)}
                  />
                );
                prev.current = next;
                break;
              }
              case SVGPathData.SMOOTH_CURVE_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };
                const previousCommand = commands[i - 1];
                const reflectedCp1 = {
                  x:
                    previousCommand &&
                      (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
                        previousCommand.type === SVGPathData.CURVE_TO)
                      ? previousCommand.relative
                        ? previousCommand.x2 - previousCommand.x
                        : previousCommand.x2 - prev.current.x
                      : 0,
                  y:
                    previousCommand &&
                      (previousCommand.type === SVGPathData.SMOOTH_CURVE_TO ||
                        previousCommand.type === SVGPathData.CURVE_TO)
                      ? previousCommand.relative
                        ? previousCommand.y2 - previousCommand.y
                        : previousCommand.y2 - prev.current.y
                      : 0,
                };
                const cp1 = {
                  x: prev.current.x - reflectedCp1.x,
                  y: prev.current.y - reflectedCp1.y,
                };
                const cp2 = {
                  x: c.relative ? prev.current.x + c.x2 : c.x2,
                  y: c.relative ? prev.current.y + c.y2 : c.y2,
                };
                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;

                prev.elems.push(
                  <path
                    key={key}
                    d={`M ${prev.current.x},${prev.current.y} ${encodeSVGPath({
                      type: SVGPathData.CURVE_TO,
                      relative: false,
                      ...next,
                      x1: cp1.x,
                      y1: cp1.y,
                      x2: cp2.x,
                      y2: cp2.y,
                    })}`}
                    {...style(key)}
                  />
                );
                prev.current = next;
                break;
              }
              case SVGPathData.QUAD_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };

                prev.current = next;
                break;
              }
              case SVGPathData.SMOOTH_QUAD_TO: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };

                const backTrackCP = (
                  // index: number,
                  // currentPoint: { x: number; y: number }
                  index, currentPoint
                ) => {
                  const previousCommand = commands[index - 1];
                  if (!previousCommand) {
                    return currentPoint;
                  }
                  if (previousCommand.type === SVGPathData.QUAD_TO) {
                    return {
                      x: previousCommand.relative
                        ? currentPoint.x - (previousCommand.x1 - previousCommand.x)
                        : currentPoint.x - (previousCommand.x1 - currentPoint.x),
                      y: previousCommand.relative
                        ? currentPoint.y - (previousCommand.y1 - previousCommand.y)
                        : currentPoint.y - (previousCommand.y1 - currentPoint.y),
                    };
                  }
                  if (previousCommand.type === SVGPathData.SMOOTH_QUAD_TO) {
                    if (!prev.previousCP) {
                      return currentPoint;
                    }
                    return {
                      x: currentPoint.x - (prev.previousCP.x - currentPoint.x),
                      y: currentPoint.y - (prev.previousCP.y - currentPoint.y),
                    };
                  }
                  return currentPoint;
                };

                const cp1 = backTrackCP(i, prev.current);

                prev.previousCP = cp1;

                prev.current = next;
                break;
              }
              case SVGPathData.ARC: {
                if (isNaN(prev.start.x)) {
                  prev.start = prev.current;
                }
                const next = {
                  x: c.relative ? prev.current.x + c.x : c.x,
                  y: c.relative ? prev.current.y + c.y : c.y,
                };

                const key = `${keyFor(c, i)}_${c.x}_${c.y}_${render_id}`;

                prev.elems.push(
                  <path
                    key={key}
                    d={`M ${prev.current.x},${prev.current.y} ${encodeSVGPath(c)}`}
                    {...style(key)}
                  />
                );



                prev.current = next;
                break;
              }
              default: {
                assertNever(c);
              }
            }
            return prev;
          },
          {
            elems: [],
            overlay: [],
            current: { x: 0, y: 0 },
            start: { x: NaN, y: NaN },
            previousCP: { x: NaN, y: NaN },
          }
        );


        return [fillNode(properties), ...data.elems]

      default:
        return null
    }
  }, [fillNode, style])

  return (
    <svg {...propertiesHand(svgObj?.properties)}>
      {svgObj.children.map(item => separateNode(item))}
    </svg>
  )
}


function SVGViewer({ svg, darwType, color }) {
  const svgObj = useMemo(() => parseSvg(svg, { viewBoxHand: false, DHand: 0 }).children[0], [svg])
  const nodes = useCreateNode(svgObj, darwType, color)


  return (
    <div>
      {nodes}
    </div>
  );
}

export default SVGViewer;
















